package visualcbir.board;

//import Grid.Cell.CellItemInterface;
//import Grid.Cell.DocCell;
//import Grid.Events.MoveEvent;
//import Grid.Events.MoveListener;
import Grid.Persist.IncMapObjectInterface;
import incVSM.Document;
import incVSM.nGramColl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 *
 * @author robertopinho
 */
public abstract class AbstractBoard  implements IncMapObjectInterface {
    private Set _listeners = new HashSet();

    Map<Integer, Row> rows = new HashMap<Integer, Row>();
    private LinkedHashSet<CellItemInterface> items = new LinkedHashSet<CellItemInterface>();
    int minRow = Integer.MAX_VALUE;
    int minCol = Integer.MAX_VALUE;
    int maxRow = Integer.MIN_VALUE;
    int maxCol = Integer.MIN_VALUE;
    protected int moveCount =0;
    private static double minDist;
    private static double maxDist;
    private double beta = 1;
    
    public AbstractBoard(){
        
    }
    
    public AbstractBoard(Reader reader,  nGramColl nGColl){
        loadXML(reader, nGColl);
    }
    
    public BoardCell getCell(int i, int j) {
        Row r = rows.get(i);
        if (r == null) {
            return null;
        } else {
            return (BoardCell) rows.get(i).cols.get(j);
        }
    }

    public List<CellItemInterface> getItems(int i, int j) {
        BoardCell c = getCell(i, j);
        if (c == null) {
            return null;
        } else {
            return c.getItems();
        }
    }

    public int itemCount(int i, int j) {
        List<CellItemInterface> cellItems = getItems(i, j);
        if (cellItems == null) {
            return 0;
        } else {
            return cellItems.size();
        }

    }

    public void moveAll() {
        for(CellItemInterface item: getItems()){
            _fireMoveEvent(item);
        }
    }

    void addItem(CellItemInterface item) {

        if (item.getRow() < minRow) {
            minRow = item.getRow();
        }
        if (item.getCol() < minCol) {
            minCol = item.getCol();
        }
        if (item.getRow() > maxRow) {
            maxRow = item.getRow();
        }
        if (item.getCol() > maxCol) {
            maxCol = item.getCol();
        }




        Row r = getRow(item.getRow());
        if (r == null) {
            r = new Row();
            rows.put(item.getRow(), r);
        }
        BoardCell c = getCell(item.getRow(), item.getCol());
        if (c == null) {
            c = new BoardCell();
            r.cols.put(item.getCol(), c);
        }
        c.items.add(item);
        item.setOnBoard(true);
        
        
        
        getItems().add(item);
        _fireMoveEvent(item);

    }

    public abstract List<CellItemInterface> getNeighbors(int i_row, int j_col);
    
    /*{
        List<CellItemInterface> neighList = new ArrayList<CellItemInterface>();
        for(int i =-1;i<2;i++){
            for(int j =-1;j<2;j++){
                if(i==0 && j==0) continue;
                List<CellItemInterface> itemNeighbors = getItems(i_row+i, j_col+j); 
                if (itemNeighbors != null) {
                    neighList.addAll(itemNeighbors);
                }
            }
        }
        
        
        return neighList;
    }*/

    CellItemInterface getOther(CellItemInterface item) {
        BoardCell c = getCell(item.getRow(), item.getCol());
        if (c.items.size() < 2) {
            return null;
        }
        if (c.items.get(0).equals(item)) {
            return c.items.get(1);
        } else {
            return c.items.get(0);
        }
    }
    //Add step distance
    public abstract int getDistance(CellItemInterface item, CellItemInterface other);
    
    
    public abstract int  getDistance(int i, int j, int x, int y);
    
    protected abstract  int getDistanceManhattan(int i, int j, int x, int y);
    
    protected abstract int getDistanceStep(int i, int j, int x, int y);


    public int itemCount() {
        return getItems().size();
    }

    void move(CellItemInterface item, int deltaI, int deltaJ) {
        
        if (deltaI == 0 && deltaJ == 0) {
            return;
        }
        moveCount++;
        BoardCell previous = getCell(item.getRow(), item.getCol());


        previous.getItems().remove(item);

        item.setRow(item.getRow() + deltaI);
        item.setCol(item.getCol() + deltaJ);
        
        
        addItem(item);
        
        _fireMoveEvent(item);
    }

    void removeItem(CellItemInterface item) {
        item.setOnBoard(false);
        BoardCell previous = getCell(item.getRow(), item.getCol());
        previous.getItems().remove(item);
        this.getItems().remove(item);
        
        _fireMoveEvent(item, true);
    }

    private String getCoordString(CellItemInterface item) {
        return new String (item.getRow() + ";"  + item.getCol());
    }

    private Row getRow(int row) {
        Row r = rows.get(row);
        if (r == null) {
            return null;
        } else {
            return r;
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer("");
        for (int i = minRow; i <= maxRow; i++) {
            for (int j = minCol; j <= maxCol; j++) {
                int itCount = itemCount(i, j);
                if (itCount == 0) {
                    sb.append("|.\t");
                } else {
                    sb.append("|" + getItems(i, j).get(0).toString());
                    if (itCount > 1) {
                        sb.append("+\t");
                    } else {
                        sb.append("\t");
                    }
                }

            }
            sb.append("|\n");

        }
        return sb.toString();

    }

    public int getMoveCount() {
        return moveCount;
    }
    /////////////////
    //Event control//
    /////////////////
        public synchronized void addMoveListener( MoveListener l ) {
        l.setBoard(this);
        _listeners.add( l );
        
    }
    
    public synchronized void removeMoveListener( MoveListener l ) {
        _listeners.remove( l );
    }
     
    protected synchronized void _fireMoveEvent(CellItemInterface item) {
        MoveEvent move = new MoveEvent( this, item );
        Iterator listeners = _listeners.iterator();
        while( listeners.hasNext() ) {
            ( (MoveListener) listeners.next() ).moveReceived( move );
        }
    }
    
    protected  synchronized void _fireMoveEvent(CellItemInterface item, boolean remove) {
        MoveEvent move = new MoveEvent( this, item, remove );
        Iterator listeners = _listeners.iterator();
        while( listeners.hasNext() ) {
            ( (MoveListener) listeners.next() ).moveReceived( move );
        }
    }

    public LinkedHashSet<CellItemInterface> getItems() {
        return items;
    }
    
public String toXML() {
        StringBuffer outBuffer = new StringBuffer();
        
        
        for(CellItemInterface item: getItems()){
            outBuffer.append(item.toXML());
        }
        
        
        
        return outBuffer.toString();
    }
    
private void loadXML(Reader reader, nGramColl nGColl){
        String[] fields;
        int row;
        int col;
        String url;
        File origFile;
        Document doc;
        String title;
        try {
            BufferedReader bReader = new BufferedReader(reader);
            String line;
            CellItemInterface item;
            int key = 0;
            while ((line = bReader.readLine()) != null) {
                
                fields = line.split("\\|");
                if(fields.length<5) continue;
                row = Integer.parseInt(fields[0]);
                col = Integer.parseInt(fields[1]);
                url = fields[2];
                origFile = new File(fields[3]);
                title = fields[4];
                doc = new Document(nGColl, key, origFile);
                
                doc.loadDocument(url,bReader, title);
                
                
                item = new DocCell(doc);
                item.setRow(row);
                item.setCol(col);
                this.addItem(item);
                
                
            }
        } catch (IOException ex) {
            Logger.getLogger(AbstractBoard.class.getName()).log(Level.SEVERE, null, ex);
        }
    
}    

/**
 * Save GraphML file of the board
 * @param GXMLFile
 */
public HashMap<String,Integer> toGXML(File GXMLFile) {

        HashMap<String,Integer> coordIdMap = new HashMap<String, Integer>();
        
        PrintWriter out = null;
        String label;
        try {
            out = new PrintWriter(GXMLFile);

            out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            out.println("<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">");
            out.println("<key id=\"name\" for=\"node\" attr.name=\"name\" attr.type=\"string\"/>");
            out.println("<key id=\"label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>");
            out.println("<key id=\"coord\" for=\"node\" attr.name=\"coord\" attr.type=\"string\"/>");
            out.println("<key id=\"weight\" for=\"edge\" attr.name=\"weight\" attr.type=\"float\"/>");
            out.println("<key id=\"year\" for=\"edge\" attr.name=\"year\" attr.type=\"float\"/>");
            out.println("<graph edgedefault=\"undirected\">");


            int count = 0;
            Integer Id = new Integer(count);

            HashMap<CellItemInterface, Integer> map = new HashMap<CellItemInterface, Integer>(this.getItems().size());
            String name;
            String coord;

            for (CellItemInterface item : getItems()) {
                name = item.getLabel();
                
                label = name.replaceAll("\\W", "");
                
                name = new String (item.getRow() + ","  + item.getCol());
                coord = getCoordString(item);
                
                out.println("<node id=\"" + Id + "\">");
                out.println("\t<data key=\"name\">" + name + "</data>");
                out.println("\t<data key=\"label\">" + label + "</data>");
                out.println("\t<data key=\"coord\">" + coord + "</data>");
                out.println("</node>");


                map.put(item, Id);
                coordIdMap.put(coord, Id);
                count++;
                Id = new Integer(count);
            }
            Integer otherId;

            maxDist = Double.NEGATIVE_INFINITY;
            minDist = Double.POSITIVE_INFINITY;
            for (CellItemInterface item : getItems()) {
                for (CellItemInterface other : this.getNeighbors(item.getRow(), item.getCol())) {
                    if(item.getDistance(other)> maxDist) {
                        maxDist = item.getDistance(other);
                    }
                    if(item.getDistance(other)< minDist) {
                        minDist = item.getDistance(other);
                    }
                }
            }
            for (CellItemInterface item : getItems()) {
                Id = map.get(item);
                for (CellItemInterface other : this.getNeighbors(item.getRow(), item.getCol())) {
                    otherId = map.get(other);
                    if (Id < otherId) {
                        
                        //If diagonal, skip
                        //if(Math.abs(item.getCol()-other.getCol())+ 
                        //   Math.abs(item.getRow()-other.getRow())==2 ) continue;
                        
                        out.println("<edge source=\"" + Id + "\" " + "target=\"" + otherId + "\">");
                        out.println("\t<data key=\"weight\">" + (item.getDistance(other)-minDist)/(maxDist-minDist) + "</data>");
                        out.println("\t<data key=\"year\">" + item.getDistance(other) + "</data>");
                        out.println("</edge>");
                    }
                }
            }


            out.println("</graph>");
            out.println("</graphml>");
        } catch (FileNotFoundException ex) {
            Logger.getLogger(AbstractBoard.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            out.close();
        }
        return coordIdMap;
        
    }

/**
 * Save incremental GraphML file
 * @param GXMLFile
 */
public void toIncGXML(File GXMLFile, HashMap<String,Integer> coordIdMap, 
        List<MoveEvent> moveList) {

        PrintWriter out = null;
        String label;
        String coord;
        
        
        List<CellItemInterface> newCells = new ArrayList<CellItemInterface>();
        List<CellItemInterface> oldCells = new ArrayList<CellItemInterface>();
        Set<CellItemInterface> allCells = new HashSet<CellItemInterface>();
        
        Integer id;
        
        
        int count = coordIdMap.size();
        for(MoveEvent event : moveList){
            CellItemInterface movedItem = event.movedItem();
            allCells.add(movedItem);
        }
        
        
        
        for(CellItemInterface movedItem : allCells){
            
            coord = getCoordString(movedItem);
            id = coordIdMap.get(coord);
            if(id==null){
                Integer newId = new Integer(count);
                coordIdMap.put(coord, newId);
                newCells.add(movedItem);
                count++;
            } else {
                oldCells.add(movedItem);
            }
            
        }
        String otherCoord;
        Integer Id;
                
        
        try {
            out = new PrintWriter(GXMLFile);

            out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            out.println("<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">");
            out.println("<key id=\"name\" for=\"node\" attr.name=\"name\" attr.type=\"string\"/>");
            out.println("<key id=\"label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>");
            out.println("<key id=\"coord\" for=\"node\" attr.name=\"coord\" attr.type=\"string\"/>");
            out.println("<key id=\"weight\" for=\"edge\" attr.name=\"weight\" attr.type=\"float\"/>");
            out.println("<key id=\"year\" for=\"edge\" attr.name=\"year\" attr.type=\"float\"/>");
            out.println("<graph edgedefault=\"undirected\">");


            
            

            HashMap<CellItemInterface, Integer> map = new HashMap<CellItemInterface, Integer>(this.getItems().size());
            String name;

            for (CellItemInterface item : newCells) {
                name = item.getLabel();
                
                label = name.replaceAll("\\W", "");
                
                name = new String (item.getRow() + ","  + item.getCol());
                coord = new String (item.getRow() + ";"  + item.getCol());
                
                Id = coordIdMap.get(coord);
                
                out.println("<node id=\"" + Id + "\">");
                out.println("\t<data key=\"name\">" + name + "</data>");
                out.println("\t<data key=\"label\">" + label + "</data>");
                out.println("\t<data key=\"coord\">" + coord + "</data>");
                out.println("</node>");


                
                
                
            }
            //old Cells
            for (CellItemInterface item : oldCells) {
                name = item.getLabel();
                
                label = name.replaceAll("\\W", "");
                
                name = new String (item.getRow() + ","  + item.getCol());
                coord = new String (item.getRow() + ";"  + item.getCol());
                
                Id = coordIdMap.get(coord);
                
                out.println("<node id=\"" + Id + "\">");
                out.println("\t<data key=\"name\">" + name + "</data>");
                out.println("\t<data key=\"label\">" + label + "</data>");
                out.println("\t<data key=\"coord\">" + coord + "</data>");
                out.println("</node>");

                
            }
            
            
            
            //edges
            Integer otherId;

            for (CellItemInterface item : allCells) {
                coord = getCoordString(item);
                
                Id = coordIdMap.get(coord);
                
                for (CellItemInterface other : this.getNeighbors(item.getRow(), item.getCol())) {
                    otherCoord = getCoordString(other);
                    otherId = coordIdMap.get(otherCoord);
                    //if (Id < otherId) {
                        
                        //If diagonal, skip
                        //if(Math.abs(item.getCol()-other.getCol())+ 
                        //   Math.abs(item.getRow()-other.getRow())==2 ) continue;
                        
                        out.println("<edge source=\"" + Id + "\" " + "target=\"" + otherId + "\">");
                        out.println("\t<data key=\"weight\">" + (item.getDistance(other)-minDist)/(maxDist-minDist) + "</data>");
                        out.println("\t<data key=\"year\">" + item.getDistance(other) + "</data>");
                        out.println("</edge>");
                    //}
                }
            }


            out.println("</graph>");
            out.println("</graphml>");
        } catch (FileNotFoundException ex) {
            Logger.getLogger(AbstractBoard.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            out.close();
        }
        
    }

    public double getBeta() {
        return beta;
    }

    public void setBeta(double beta) {
        this.beta = beta;
    }

    
    
}
class Row {

    Map<Integer, BoardCell> cols = new HashMap<Integer, BoardCell>();
}



